package torchtools.binary;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;

/**
 * Mode can either be "r" or "rw".
 * 
 * @author Matt
 */
public class TLPlayerSave extends TLSave {
    
    /** Typical length of GUIDs (8 bytes). */
    public static final int GUID_LENGTH = 8;
    
    /** Offset from beginning of file (4). */
    public static final int OFF_CLASS = 4;
    
    /** Offset from after the cheater byte (40). */
    public static final int OFF_NAME = 40;
    
    /** Offset from after the hardcore byte (35). */
    public static final int OFF_CHEATER = 35;
    
    /** Offset from after the name string (84). */
    public static final int OFF_LEVEL = 84;
    
    /** Offset from the start of the FF group (-8). */
    public static final int OFF_GOLD = -8;
    public static final int GOLD_FF_COUNT = 12;
    
    public static final String[] DIFFICULTIES = { "Easy", "Normal", "Hard", "Very Hard" };
    public static final int DIFFICULTY_EASY = 0;
    public static final int DIFFICULTY_NORMAL = 1;
    public static final int DIFFICULTY_HARD = 2;
    public static final int DIFFICULTY_VERY_HARD = 3;
    
    public static final byte HARDCORE_TRUE = (byte)0x01;
    public static final byte HARDCORE_FALSE = (byte)0x00;
    public static final byte CHEATER_TRUE = (byte)0xD6;
    public static final byte CHEATER_FALSE = (byte)0x4E;
    
    public static final byte[] PET_HASH = { 
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xDE 
    };
    
    public static final String INNATEARMOR = "INNATEARMOR";
    public static final HashMap<Integer, String> PET_TYPES = new HashMap<Integer, String>();
    
    //common pet types...
    public static final int PET_UNKNOWN = 0;
    public static final int PET_DOG = 1062836634;
    public static final int PET_CAT = 1061158912;
    
    static {
        PET_TYPES.put(PET_DOG, "Dog");
        PET_TYPES.put(PET_CAT, "Cat");
    }
    
    //TODO: file save replaceTLString SharedStash DOES NOT USE EOF INT
    
    protected long petTypePtr;
    protected int petTypeID = PET_UNKNOWN;
    protected String petType = "Unknown";
    protected long petNamePtr;
    protected String petName = "Unknown";
    
    protected int difficulty;
    protected long difficultyPtr;
    protected boolean hardcore;
    protected long hardcorePtr;
    protected boolean cheater;
    protected long cheaterPtr;
    protected long innateArmorPtr;
    
    protected long classNamePtr;
    protected long playerNamePtr;
    protected long levelPtr;
    protected long goldPtr;
    
    protected String playerName;
    protected String className;
    protected int level;
    protected int gold;
    
    public TLPlayerSave(String file, String mode) throws IOException {
        this(file, mode, true);
    }
    
    public TLPlayerSave(String file, String mode, boolean readStart) throws IOException {
        this(new File(file), mode, readStart);
    }
    
    public TLPlayerSave(File file, String mode) throws IOException {
        this(file, mode, true);
    }
    
    /**
     * Opens a player save file (.svt).
     * 
     * @param file the file to read
     * @param mode "r" (read) or "rw" (read-write)
     * @param readStart if true, the class will read player and gold properties
     * @throws java.io.IOException
     */
    public TLPlayerSave(File file, String mode, boolean readStart) throws IOException {
        super(file, mode);
        if (readStart) {
            readPlayerInfo();
            readGoldInfo();
        }
    }
    
    /** 
     * Convenience method to read class, hardcore mode, cheat flag, difficulty, 
     * name and level pointers. File pointer does not need to be in any specific place.
     */
    public void readPlayerInfo() throws IOException {
        //seek to the start of the file
        seek(OFF_CLASS);
        classNamePtr = getFilePointer();
        className = readTLString();
        
        difficultyPtr = getFilePointer();
        difficulty = readByte();
        
        step(3);
        hardcorePtr = getFilePointer();
        hardcore = readByte()!=0x00;
        
        step(OFF_CHEATER);
        cheaterPtr = getFilePointer();
        cheater = readByte()==CHEATER_TRUE;
        
        step(OFF_NAME);
        playerNamePtr = getFilePointer();
        playerName = readTLString();
        
        step(OFF_LEVEL);
        levelPtr = getFilePointer();
        level = readTLShort();
    }
        
    /** 
     * Searches the save file for pet info, starting at the current file pointer. 
     * Most efficient after the player's inventory has been read. Expects the
     * pointer to be before the pet info!
     */
    public void readPetInfo() throws IOException {
        //first find the 'INNATEARMOR' 
        if (innateArmorPtr<=0)
            innateArmorPtr = quickFind(INNATEARMOR.getBytes(ENCODING));
        if (innateArmorPtr!=-1 && nextByteGroup(FF, 12, true, false)) {
            petNamePtr = getFilePointer();
            petName = readTLString();
            petTypePtr = getFilePointer() - 24;
            
            seek(petTypePtr);
            petTypeID = readTLInt();
            petType = PET_TYPES.get(petTypeID);
            if (petType==null)
                petType = "Unknown";
        }
        
        /*
        //search for the pet hash
        long i = quickFind(PET_HASH);
        if (i==-1) { // no pet found
            petType = PET_TYPE_UNKNOWN;
            petName = null;
        } else {
            //make sure previous byte is one of DOG or CAT type
            seek(i);
            byte prev = previousByte();
            if (prev == PET_TYPE_DOG_GUID[PET_TYPE_DOG_GUID.length-1]
                    || prev == PET_TYPE_CAT_GUID[PET_TYPE_CAT_GUID.length-1]) {
                //first determine type
                step(-GUID_LENGTH);
                petTypePtr = getFilePointer();
                byte[] guid = new byte[GUID_LENGTH];
                read(guid);
                if (Arrays.equals(guid, PET_TYPE_DOG_GUID)) {
                    petType = PET_TYPE_DOG;
                } else if (Arrays.equals(guid, PET_TYPE_CAT_GUID)) {
                    petType = PET_TYPE_CAT;
                } else {
                    petType = PET_TYPE_UNKNOWN;
                    return;
                }
                
                step(PET_NAME_OFF);
                petNamePtr = getFilePointer();
                petName = readTLString();
            } else {
                //otherwise, step forward and call this method again
                step(PET_HASH.length);
                readPetInfo();
            }
        }*/
                
    }
    
    /** 
     * Convenience method to read gold pointers -- epects file pointer to be
     * in the right place (i.e. before the gold FF group).
     */
    public void readGoldInfo() throws IOException {
        gold = -1;
        //a group of exactly 12 FF bytes proceeds the gold int
        boolean success = nextByteGroup(FF, GOLD_FF_COUNT, true);
        if (success) {
            step(OFF_GOLD);
            goldPtr = getFilePointer();
            gold = readTLInt();
        }
    }
    
    public String getPlayerName() {
        return playerName;
    }
    
    public long getPlayerNamePtr() {
        return playerNamePtr;
    }
    
    public String getPlayerClass() {
        return className;
    }
    
    public long getPlayerClassPtr() {
        return classNamePtr;
    }
    
    public int getGold() {
        return gold;
    }
    
    public long getGoldPtr() {
        return goldPtr;
    }
    
    /** 
     * Attempts to write the specified gold amount to the file -- requires
     * goldPtr to be set. 
     */
    public void writeGold(int gold) throws IOException {
        seek(getGoldPtr());
        writeTLInt(gold);
        this.gold = gold;
    }
    
    public int getLevel() {
        return level;
    }
    
    public long getLevelPtr() {
        return levelPtr;
    }
    
    public int getDifficulty() {
        return difficulty;
    }
    
    public String getDifficultyText() {
        if (difficulty<0||difficulty>DIFFICULTIES.length)
            return "Unknown";
        return DIFFICULTIES[difficulty];
    }

    public long getDifficultyPtr() {
        return difficultyPtr;
    }
    
    /** 
     * Attempts to write the specified difficulty to the file -- requires
     * difficultyPtr to be set. 
     */
    public void writeDifficulty(int difficulty) throws IOException {
        seek(getDifficultyPtr());
        writeByte((byte)difficulty);
        this.difficulty = difficulty;
    }

    public boolean isHardcore() {
        return hardcore;
    }

    public long getHardcorePtr() {
        return hardcorePtr;
    }
    
    /** 
     * Attempts to write the specified hardcore flag to the file -- requires
     * hardcorePtr to be set. 
     */
    public void writeHardcore(boolean hardcore) throws IOException {
        seek(getHardcorePtr());
        writeByte(hardcore ? HARDCORE_TRUE : HARDCORE_FALSE);
        this.hardcore = hardcore;
    }

    public boolean isCheater() {
        return cheater;
    }
    
    public long getCheaterPtr() {
        return cheaterPtr;
    }
    
    /** 
     * Attempts to write the specified cheater flag to the file -- requires
     * cheaterPtr to be set. 
     */
    public void writeCheater(boolean cheater) throws IOException {
        seek(getCheaterPtr());
        writeByte(cheater ? CHEATER_TRUE : CHEATER_FALSE);
        this.cheater = cheater;
    }

    public long getPetTypePtr() {
        return petTypePtr;
    }

    /** Can only be used after pet pointers are set (i.e. through readPetInfo). */
    public String getPetType() {
        return petType;
    }
    
    /** Can only be used after pet pointers are set (i.e. through readPetInfo). */
    public int getPetTypeID() {
        return petTypeID;
    }
    
    /** 
     * Attempts to write the specified pet type to the file -- requires
     * petTypePtr to be set. 
     */
    public void writePetType(int petType) throws IOException {
        byte[] type = null;
        if (petTypeID==PET_UNKNOWN)
            throw new IOException("unknown pet type");
        seek(getPetTypePtr());
        write(type);
        this.petTypeID = petTypeID;
        this.petType = PET_TYPES.get(petTypeID);
        if (this.petType==null)
            this.petType = "Unknown";
    }
    
    public long getPetNamePtr() {
        return petNamePtr;
    }
    
    /** Can only be used after pet pointers are set (i.e. through readPetInfo). */
    public String getPetName() {
        return petName;
    }
}
